/*
 * Copyright (C) 2019-2020 Alibaba Group Holding Limited
 */

#include "app_config.h"
#include <stdlib.h>
#include <string.h>
#include <aos/aos.h>
#include "aos/cli.h"
#include "app_main.h"
#include "app_init.h"
//#include "kws_fxp.h"
#include "app_dma.h"
#include "ck_audio.h"
#include <voice_ai.h>
#include <k_api.h>
#include <csi_core.h>
#include <drv/timer.h>
#include <sys_freq.h>
#include <ipc.h>

#define TAG "app"

#define LPM_WAIT_CMD (0X01)
#define LPM_SERVER_ID (0X02)

int create_fe_task(const char *task_name, task_entry_t task_routine, void *arg, int prio);

typedef struct ai_param {
    int   sample_bits;      /* 采样精度 默认16bit*/
    int   channels;         /* 预留  */
    int   rate;             /* 采样率 默认16K*/
    int   nsmode;           /* 去噪等级参数  0~3 非线性处理等级逐步加强，其他值无非线性处理 */
    int   aecmode;          /* 回音消除等级  0~3 非线性处理等级逐步加强，其他值无非线性处理 */
    int   vadmode;          /* VAD等级 0~3 等级逐步加强 */
    int   sentence_time_ms; /* 有语音断句时间 */
    int   noack_time_ms;    /* 无语音超时时间 */
    int   max_time_ms;      /* 唤醒后总超时时间 */
    void *ext_param1;       /* 预留 */
    void *ext_param2;       /* 预留 */
    int   vadswitch;        /* 0 关闭VAD，1 打开, 2 打开起点关闭尾点 */
    int   vadfilter;        /* VAD过滤器类型， 0 关闭过滤器， 1~3 不同过滤器类型 */
    int   vadkws_strategy;  /* VAD KWS策略 */
    MitRtosVadThres vadthresh_kws;    /* KWS VAD阈值 */
    MitRtosVadThres vadthresh_asr;    /* ASR VAD阈值 */ 
    int   wwv_enable;        /* 使能算法侧唤醒二次确认功能 */
} ai_alg_param_t;

typedef struct {
    ai_alg_param_t  *palg;
    voice_kwmap_t   *kw_map;
} ai_hdl_t;

// static void profiling()
// {
//     timer_handle_t g_timer;
//     uint32_t audio_alg_start = 0, audio_alg_end = 0, audio_alg_div = 0;

//     g_timer = csi_timer_initialize(1, NULL);
//     csi_timer_config(g_timer, TIMER_MODE_FREE_RUNNING);
//     csi_timer_start(g_timer);

//     csi_timer_get_current_value(g_timer, &audio_alg_start);
    

//     csi_timer_get_current_value(g_timer, &audio_alg_end);
//     printf("start is %u, end is %u\n", audio_alg_start, audio_alg_end);
//     if(audio_alg_start > audio_alg_end) {
//         audio_alg_div = audio_alg_start - audio_alg_end;
//         printf("*****************timer profiling result is %d us\n", audio_alg_div / (drv_get_timer_freq(1) / 1000000));
//     }
// }

void alg_init(ai_alg_param_t *param)
{
    log_setLevel(Error);
	
	audio_timeout_config(param->sentence_time_ms, param->noack_time_ms, param->max_time_ms);

    LOGD(TAG, "audio init %d %d %d %d %d %f %f\n", 
        param->vadswitch ? 1 : 0, 
        param->wwv_enable, 
        param->vadswitch == 2 ? 1 : 0,
        param->vadfilter,
        param->vadkws_strategy,
        param->vadthresh_kws.vad_speech_noise_thres_ep,
        param->vadthresh_asr.vad_speech_noise_thres_sp);
        
    audio_init(20*2*16, 
        param->vadswitch ? 1 : 0, 
        param->wwv_enable, 
        param->vadswitch == 2 ? 1 : 0, 
        param->vadfilter,
        param->vadkws_strategy,
        (MitRtosVadThres *)&param->vadthresh_kws,
        (MitRtosVadThres *)&param->vadthresh_asr
    );

    // mit_rtos_debug_set_mode(0x20);
    
	// int ret = mit_rtos_selfcheck(1,1,1,0x20, kMitRtosAlgPMWFPG);
	// LOGE(TAG, "call mit_rtos_selfcheck() return %d", ret);
	// ret = 0;
}


static voice_t *   g_voice;
static aos_sem_t   g_sem;
static aos_event_t asr_evt;
static int         g_pcm_cnt;
static char *      pcm_data;
static int         g_asr_cnt = 0;

#define KWS_EVT_OK (1)
#define KWS_EVT_FAIL (2)

#define VAD_DATA_MS (10 * 16 * 2) //10*16000*16/8

void start_dma1_channel4(void *src, void *dst, size_t size);
void wait_for_dma1_channel4_idle();

#define BACLFLOW_LEN 4 //(1024 * 50)
typedef struct {
    char data[BACLFLOW_LEN];
    int  len;
} backflow_t;
static backflow_t backflow[4] = {0};

// kws add by liyd 20200415
#define FRAME_SIZE (160)
#define BATCH_SIZE (9)
//kws_fxp_t *              kws_ptr = NULL;
extern const void *const g_kws_model_mit_fsmn_batch;
// end kws

typedef enum {
    CHECKER_STATE_BREAK,    // stop the main loop and clean up;
    CHECKER_STATE_CONTINUE, // continue with the next frame;
    CHECKER_STATE_RESET     // will reset some status and continue;
} CHECKER_STATE;

// A callback for passing audio waves to the kws.
typedef size_t (*READER)(void *   src,    // points to whatever user specified;
                         size_t   length, // the length of audio data per frame;
                         int16_t *wave);  // fill audio data to this address;

// A callback method for passing out the kws result.
// Return a control flag.
typedef CHECKER_STATE (*CHECKER)(void *reference, // points to whatever user specified;
                                 int   result);     // the keyword id if spotted, otherwise -1;

static void spot_keywords_baola(void *reader_params, READER reader, void *checker_params,
                                CHECKER checker);

static void backflow_copy(int id, void *data, int len)
{
    return;
    backflow_t *b = &backflow[id];

    if ((b->len + len) < BACLFLOW_LEN) {
        memcpy(b->data + b->len, data, len);
        b->len += len;
    }
}

// static void backflow_reset(void)
// {
//     backflow[0].len = 0;
//     backflow[1].len = 0;
//     backflow[2].len = 0;
//     backflow[3].len = 0;
// }

static size_t read_wave_for_kws(void *src, size_t size, int16_t *dst)
{
    int len = size * 2;

    aos_sem_wait(&g_sem, AOS_WAIT_FOREVER);

    memcpy((char *)dst, pcm_data, len);
    backflow_copy(3, pcm_data, 2560);

    g_pcm_cnt++;

    return size;
}

static CHECKER_STATE show_kws_result(void *source, int result)
{
    if (result >= 0) {
        g_asr_cnt++;
        aos_event_set(&asr_evt, KWS_EVT_OK, AOS_EVENT_OR);
        LOGE(TAG, "baola baola!");
    } else {
        aos_event_set(&asr_evt, KWS_EVT_FAIL, AOS_EVENT_OR);
    }

    return CHECKER_STATE_CONTINUE;
}

// DMA
void pm_dma_initialize(void)
{
    /* do nothing. */
}

void pm_dma_fetch_async(void *src, void *dst, size_t size)
{
    start_dma1_channel4(src, dst, size);
}

void pm_dma_await_idle()
{
    wait_for_dma1_channel4_idle();
}

// FIXME:
void md_dma_featch_async(void *dsr, const void *src, size_t size)
{
}

// Attention:
//     Should only be used by the parameter manager of nnet.
void start_dma1_channel4(void *src, void *dst, size_t size)
{
    dw_dma_reg_t *addr = DMAC0_BASE;

    addr->CHN1_REG.SARx = (uint32_t)src;
    addr->CHN1_REG.DARx = (uint32_t)dst;

    static size_t previous_size = 0;

    if (previous_size != size) {
        previous_size = size;
        // Due to the configuration, the block size is number of
        // four bytes, and due to the "size" input means the number
        // of bytes, we MUST divided the size by 4 before set to
        // the register;
        // This channel should be used only by the parameter manager
        // of the nnet module, in which all the chunk sizes are
        // aligned by 4.
        // But should still be very careful at here.
        addr->CHN1_REG.CTLHx = (size + 0x3) >> 2;
    }

    // Enable.
    addr->ChEnReg = 0x202;

    addr->ReqSrcReg = 0x202;
}

void wait_for_dma1_channel4_idle()
{
    dw_dma_reg_t *addr = DMAC0_BASE;

    while ((addr->RawBlock & 0x2) != 0x2)
        ;

    addr->ClearBlock = 0x2;
}

int is_dma1_channel5_idle()
{
    dw_dma_reg_t *addr = DMAC0_BASE;
    return (addr->RawBlock & 0x4) == 0x0;
}

void wait_for_dma1_channel5_idle()
{
    dw_dma_reg_t *addr = DMAC0_BASE;

    // Clear will happens in interrupt response,
    // no worry, just make sure the interrupt is
    // called.
    while ((addr->RawBlock & 0x4) != 0x0) {
    }
}

void start_dma1_channel5(void *src, void *dst, size_t size)
{
    dw_dma_reg_t *addr = DMAC0_BASE;

    addr->CHN2_REG.SARx  = (uint32_t)src;
    addr->CHN2_REG.DARx  = (uint32_t)dst;
    addr->CHN2_REG.CTLHx = (size + 0x3) >> 2;

    addr->ChEnReg   = 0x404;
    addr->ReqSrcReg = 0x404;
}

void dmac1_channel4_config()
{
    dw_dma_reg_t *addr = DMAC0_BASE;

    addr->CHN1_REG.CTLLx &= ~(0x7 << 20);
    addr->CHN1_REG.CTLLx &= ~(0x7 << 4);
    addr->CHN1_REG.CTLLx |= 0x2 << 4; // SRC_TR_WIDTH: 32 bits
    addr->CHN1_REG.CTLLx &= ~(0x7 << 1);
    addr->CHN1_REG.CTLLx |= 0x2 << 1; // DST_TR_WIDTH: 32 bits
    addr->CHN1_REG.CTLLx &= ~(0x7 << 14);
    addr->CHN1_REG.CTLLx |= 0x3 << 14; // SRC_MSIZE
    addr->CHN1_REG.CTLLx &= ~(0x7 << 11);
    addr->CHN1_REG.CTLLx |= 0x3 << 11; // DERC_MSIZE
    addr->CHN1_REG.CTLLx &= ~(0x3 << 9);
    addr->CHN1_REG.CTLLx &= ~(0x3 << 7); // DINC: Increment
    addr->CHN1_REG.CTLLx |= 0x1;         // Channel 0 interrupt enable
    addr->CHN1_REG.CFGLx |= 0x1 << 11;
    addr->CHN1_REG.CFGHx &= ~(0x1 << 1); // FIFO_MODE
    addr->CHN1_REG.CFGHx &= ~(0xF << 7);
    addr->CHN1_REG.CFGHx |= 0x1 << 7; // SRC_PER

    addr->DmaCfgReg = 0x1;
}

void dmac1_channel5_config()
{
    dw_dma_reg_t *addr = DMAC0_BASE;

    addr->CHN2_REG.CTLLx &= ~(0x7 << 20);

    addr->CHN2_REG.CTLLx &= ~(0x7 << 4);
    addr->CHN2_REG.CTLLx |= 0x2 << 4; // SRC_TR_WIDTH: 32 bits
    addr->CHN2_REG.CTLLx &= ~(0x7 << 1);
    addr->CHN2_REG.CTLLx |= 0x2 << 1; // DST_TR_WIDTH: 32 bits
    addr->CHN2_REG.CTLLx &= ~(0x7 << 14);
    addr->CHN2_REG.CTLLx |= 0x3 << 14; // SRC_MSIZE
    addr->CHN2_REG.CTLLx &= ~(0x7 << 11);
    addr->CHN2_REG.CTLLx |= 0x3 << 11; // DERC_MSIZE
    addr->CHN2_REG.CTLLx &= ~(0x3 << 9);

    addr->CHN2_REG.CTLLx &= ~(0x3 << 7); // DINC: Increment
    addr->CHN2_REG.CTLLx |= 0x1;         // Channel interrupt enable

    addr->CHN2_REG.CFGLx |= 0x1 << 11;
    addr->CHN2_REG.CFGHx &= ~(0x1 << 1); // FIFO_MODE
    addr->CHN2_REG.CFGHx &= ~(0xF << 7);
    addr->CHN2_REG.CFGHx |= 0x1 << 7; // SRC_PER

    // addr->MaskBlock = 0x404; // Block interrupt enable
    addr->MaskBlock = 0x00; // Block interrupt enable
}

static void initialize_dma1()
{
    dw_dma_reg_t *addr = DMAC0_BASE;

    dmac1_channel4_config();
    dmac1_channel5_config();

    // addr->MaskBlock = 0x101; // Block interrupt enable 318
    addr->MaskBlock = 0x00; // Block interrupt enable 318
    addr->DmaCfgReg = 0x1;   // Enable DMAC 398
    addr->ChEnReg   = 0x101; // Enable channel 0 3A0

    asm("psrset ie");
}

void kws_entry(void *arg)
{
    LOGE(TAG, "kws init finished");

#if (CONFIG_SPLIT_KWS_DNN > 0)
    spot_keywords(NULL, read_wave_for_kws, NULL, show_kws_result, 3);
#else
    spot_keywords_baola(NULL, read_wave_for_kws, NULL, show_kws_result);

#endif
}

static int init(void *priv)
{
    ai_hdl_t *hdl = (ai_hdl_t *)priv;
    aos_event_new(&asr_evt, 0);
    initialize_dma1();
	
    LOGD(TAG, "alg init\n");
	#ifdef DEBUG_MITRTOS_DISABLE
	#else
	alg_init(hdl->palg);
	#endif

    return 0;
}

static int get_kw_map(void *priv, voice_kwmap_t **kw_map)
{
    ai_hdl_t *hdl = (ai_hdl_t *)priv;
    int count;

    aos_check_param(kw_map);

    if (!hdl->kw_map) {
        hdl->kw_map = aos_zalloc_check(sizeof(voice_kwmap_t));
    }

    command_word_handle *kmap = audio_get_kws_map();
    if (kmap && kmap->inited && kmap->count > 0) {
        count = kmap->count;
        hdl->kw_map->count = count;
        hdl->kw_map->map = aos_zalloc_check(sizeof(voice_kwid_t) * count);

        for (int i = 0; i < count; ++i) {
            hdl->kw_map->map[i].id = kmap->map[i].id;
            hdl->kw_map->map[i].kword = kmap->map[i].word;
            csi_dcache_clean_invalid_range((uint32_t *)hdl->kw_map->map[i].kword, COMMANDWORDLENGTH);
        }
        csi_dcache_clean_invalid_range((uint32_t *)hdl->kw_map->map, sizeof(voice_kwid_t) * count);
    }

    *kw_map = hdl->kw_map;
    return 0;
}

static int set_param(void *priv, void *param)
{
    ai_hdl_t *hdl = (ai_hdl_t *)priv;
    
    if (!hdl->palg) {
        hdl->palg = aos_malloc_check(sizeof(ai_alg_param_t));
    }

    *(hdl->palg) = *(ai_alg_param_t *)param;

    int *pprt = (int *)hdl->palg;
    printf("voice ai param init:\n");
    for (int i = 0; i < 19; i++) {
        printf("%d\n", *pprt++);
        if ((i + 1) % 5 == 0) {
            printf("\n");
        }
    }
    printf("*****************************\n");

    return 0;
}

// static int deinit(void *priv)
// {
//     // destory
//     //kws_fxp_destroy(kws_ptr);

//     aos_event_free(&asr_evt);

//     return 0;
// }

static char vad_out_data[5120];
static int vad(void *priv, void *mic, void *ref, int ms, void **out, size_t *vad_len)
{
    static int vad_detected = 0;
    int vad_stat = VOICE_VADSTAT_SILENCE;

    aos_check_param(vad_len && out && vad_len);
    *vad_len = 0;
    *out = NULL;

    /* VAD 状态处理 */
    int vadres = audio_get_vad();
    switch(vadres) {
        case -1:
            break;
        case 0:
            /* 进入下一个状态 */
#ifdef DEBUG_LOG_MX_ENABLE
            LOGD(TAG, "get vad_sp");
#endif
            if (AU_STATUS_WAIT == audio_get_status()) {
#ifdef DEBUG_LOG_MX_ENABLE
                LOGD(TAG, "change AU_STATUS_WAIT to AU_STATUS_ASR");
#endif
                audio_set_status(AU_STATUS_ASR);
                vad_stat = VOICE_VADSTAT_ASR;
            }
            vad_detected = 1;
            break;
        case 1:
#ifdef DEBUG_LOG_MX_ENABLE
            LOGD(TAG, "get vad_ep, but waiting for asr data finish");
#endif
            vad_detected = 0;
            break;
        case 2:
#ifdef DEBUG_LOG_MX_ENABLE
            LOGD(TAG, "get vad_timeout, do nothing");
#endif
            vad_detected = 0;
            break;
        default:
            ;
    }

    int vad_data_len;
    int vad_data_finished = 0;
    char *vad_data = audio_get_vad_data(&vad_data_len, &vad_data_finished);
    if (vad_data != NULL) {
        // printf("vad len:%d\n", vad_data_len);
        memcpy(vad_out_data, vad_data, vad_data_len);
        *vad_len = vad_data_len;
        *out = vad_out_data;
    }
    if (1 == vad_data_finished) {
#ifdef DEBUG_LOG_MX_ENABLE
        LOGD(TAG, "vad data finished. call yv_vad_send(0)");
#endif
    }

    if (audio_get_status() == AU_STATUS_FINI) {
        vad_stat = VOICE_VADSTAT_ASR_FINI;
        
        /* 进入下一个状态 */
        audio_set_status(AU_STATUS_READY);
    }

    return vad_stat != VOICE_VADSTAT_SILENCE ? vad_stat : (vad_detected ? VOICE_VADSTAT_VOICE : VOICE_VADSTAT_SILENCE);
}

static int kws(void *priv, void *mic, void *ref, int ms, void *vad)
{
    aos_check_param(mic);

    int kwid = audio_process(mic, ms * 16);

    /* 强制唤醒 */
    // if (g_yv_handle->asr_wake_force)
    // {
    //     kwid= 0;
    //     audio_set_status(AU_STATUS_WAKE);
    //     g_yv_handle->asr_wake_force = 0;
    // }

    /* 状态处理 */
    int au_ret = audio_get_status();

    switch (au_ret) {
    case AU_STATUS_READY:
        break;
    case AU_STATUS_WAKE:
        // if (kwid & KWS_ID_WWV_MASK) {
        //     kws_data_needed = 1;
        // } else {
        //     kws_data_needed = 0;
        // }

        /* 进入下一个状态 */
        audio_set_status(AU_STATUS_WAIT);
        break;
    case AU_STATUS_WAIT:
        break;
    case AU_STATUS_ASR:
        break;
    case AU_STATUS_FINI:
        /* 结束处理 */
#ifdef DEBUG_LOG_MX_ENABLE
        LOGD(TAG, "DSP finish, call yv_sentence_timout? %d", g_yv_handle->asr_enable);
#endif
        // if(g_yv_handle->asr_enable > 0) {
        //     yv_sentence_timout(g_yv_handle);
        // }
        break;
    default:
        ;
    }

    if (kwid > 0) {
        printf("kwid is %d\n", kwid);
    }
    return kwid & 1 ? kwid : 0;
}

static int get_kws_data(void *priv, void **data, size_t *data_len)
{
    aos_check_param(data);

    /* wwv data ready */
    int kws_data_len;
    char *kws_data = audio_get_kws_data(&kws_data_len);
    if (kws_data != NULL) {
        *data = kws_data;
        *data_len = (size_t)kws_data_len;
        LOGD(TAG, "kws get %p %d bytes data", kws_data, kws_data_len);
        return 0;
    }

    *data = NULL;
    *data_len = 0;
    return -1;
}

// static int aec(void *priv, void *mic, void *ref, int ms, void *out)
// {
//     return 0;
// }

// static int asr(void *priv, void *vad_data, int ms)
// {
//     unsigned int flags = 0;

//     aos_event_get(&asr_evt, KWS_EVT_OK | KWS_EVT_FAIL, AOS_EVENT_OR_CLEAR, &flags, -1);

//     if (flags & KWS_EVT_OK) {
//         return 1;
//     } else {
//         return -1;
//     }
// }

static int wkvc_stat(void *priv, int is_playing)
{
    audio_wakeup_voice_stat(is_playing, 20, 200);
    return 0;
}

static int start_alg_tsk(void *priv, void (*fn)(void *), void *arg)
{
    return create_fe_task("alg", fn, arg, AOS_DEFAULT_APP_PRI - 4);
}

voice_cts_ops_t vocice_ops = {
    .cache_cts = 20,
    .init   = init,
    // .deinit = deinit,
    .start_alg_tsk = start_alg_tsk,
    .set_param = set_param,
    .wkvc_stat = wkvc_stat,
    // .aec    = aec,
    .kws    = kws,
    .vad    = vad,
    // .asr    = asr,
    .get_kws_data    = get_kws_data,
    .get_kw_map = get_kw_map,
};

static void _ipc_process(ipc_t *ipc, message_t *msg, void *priv)
{
    if (msg->command == LPM_WAIT_CMD) {
        *(volatile uint32_t *)0xe000e140 |= 0x30;
        LOGD(TAG, "cpu2 lpm enter");
        ipc_message_ack(ipc, msg, AOS_WAIT_FOREVER);
        asm("wait");
        LOGD(TAG,"cpu2 lpm leave");
    }
}

static void lpm_init(void)
{
    ipc_t *ipc;

    ipc = ipc_get(0);

    ipc_add_service(ipc, LPM_SERVER_ID, _ipc_process, NULL);
}

void voice_app_init(void)
{
    aos_sem_new(&g_sem, 0);

    ai_hdl_t *prm = aos_zalloc_check(sizeof(ai_hdl_t));
    g_voice = voice_ai_init(prm, &vocice_ops);

    aos_task_new("kws", kws_entry, NULL, 28 * 1024);
    
    lpm_init();
}

int16_t wave_frame[FRAME_SIZE * BATCH_SIZE] = {0};

void spot_keywords_baola(void *reader_params, READER reader, void *checker_params, CHECKER checker)
{
    // TODO:
    /*
    const size_t input_sequence_num  = 9;
    const size_t output_sequence_num = 3;

    while (reader(reader_params, FRAME_SIZE * (input_sequence_num - 1), wave_frame)) {
        const int *kws_result = kws_fxp_run(kws_ptr, wave_frame);
        for (size_t i = 0; i < output_sequence_num; ++i) {
            checker(checker_params, *kws_result++);
        }
    }*/
}

#define FE_TASK_STACK_SIZE (1024 * 32 / 4)
static cpu_stack_t g_fe_task_stack[FE_TASK_STACK_SIZE] __attribute__((section(".fe_stack")));
static ktask_t     g_fe_task_handle;

int create_fe_task(const char *task_name, task_entry_t task_routine, void *arg, int prio) 
{
    return krhino_task_create(&g_fe_task_handle, task_name, arg,
                        prio, 0u, g_fe_task_stack,
                        FE_TASK_STACK_SIZE, task_routine, 1u);
}
